"""
配置管理模块：PBPK建模系统配置管理

该模块实现了PBPK建模系统的配置管理功能，包括：
- 配置文件读写
- 参数验证
- 环境配置
- 模型参数管理

作者：BlackCat@CPPO
版本：1.0.0
"""

import json
import yaml
import os
from typing import Dict, Any, Optional, Union
import warnings

class ConfigManager:
    """
    配置管理器类
    
    该类管理PBPK建模系统的所有配置参数，包括药物参数、生理参数、
    模型参数等，支持JSON和YAML格式的配置文件。
    """
    
    def __init__(self, config_path: Optional[str] = None):
        """
        初始化配置管理器
        
        参数:
            config_path (str, optional): 配置文件路径，如果为None则使用默认配置
        """
        self.config_path = config_path
        self.config = {}
        self.default_config = self._get_default_config()
        
        if config_path and os.path.exists(config_path):
            self.load_config(config_path)
        else:
            self.config = self.default_config.copy()
    
    def _get_default_config(self) -> Dict[str, Any]:
        """获取默认配置"""
        return {
            "drug": {
                "name": "Generic_Drug",
                "dose": 100.0,
                "unit": "mg",
                "route": "oral",
                "formulation": "immediate_release",
                "solubility": 1.0,
                "permeability": 1e-4,
                "logP": 2.0,
                "pKa": 7.4,
                "molecular_weight": 300.0,
                "fu": 0.1,
                "BP": 1.0
            },
            "absorption": {
                "ka": 0.5,
                "kd": 1.0,
                "fa": 0.8,
                "fg": 0.9,
                "fh": 0.7,
                "ts": 0.5,
                "tlag": 0.0,
                "absorption_model": "first_order"
            },
            "distribution": {
                "Vp": 3.0,
                "Ve": 12.0,
                "Vss": 15.0,
                "model": "full_pbpk",
                "tissue_partition_method": "poulin_theil",
                "blood_brain_barrier": True,
                "placental_transfer": False
            },
            "elimination": {
                "CL_hepatic": 0.5,
                "CL_renal": 0.3,
                "CL_biliary": 0.1,
                "CL_other": 0.05,
                "metabolism_model": "well_stirred",
                "renal_model": "GFR_based"
            },
            "population": {
                "population_type": "adult",
                "age": 35.0,
                "weight": 70.0,
                "height": 170.0,
                "gender": "male",
                "ethnicity": "caucasian",
                "disease_state": "healthy",
                "genetic_variants": []
            },
            "simulation": {
                "time_span": 24.0,
                "time_step": 0.1,
                "output_interval": 1.0,
                "solver": "LSODA",
                "tolerance": 1e-6,
                "max_steps": 10000
            },
            "output": {
                "format": "csv",
                "directory": "./output",
                "prefix": "pbpk_results",
                "save_plots": True,
                "plot_format": "png",
                "plot_dpi": 300
            },
            "validation": {
                "enable_validation": True,
                "validation_data_path": None,
                "validation_metrics": ["rmse", "mae", "r_squared"],
                "acceptance_criteria": {
                    "rmse_threshold": 0.2,
                    "mae_threshold": 0.15,
                    "r_squared_threshold": 0.85
                }
            }
        }
    
    def load_config(self, config_path: str) -> None:
        """
        从文件加载配置
        
        参数:
            config_path (str): 配置文件路径
            
        异常:
            FileNotFoundError: 文件不存在
            ValueError: 文件格式不支持
        """
        if not os.path.exists(config_path):
            raise FileNotFoundError(f"配置文件不存在: {config_path}")
        
        file_extension = os.path.splitext(config_path)[1].lower()
        
        try:
            if file_extension == '.json':
                with open(config_path, 'r', encoding='utf-8') as f:
                    loaded_config = json.load(f)
            elif file_extension in ['.yaml', '.yml']:
                with open(config_path, 'r', encoding='utf-8') as f:
                    loaded_config = yaml.safe_load(f)
            else:
                raise ValueError(f"不支持的配置文件格式: {file_extension}")
            
            # 合并配置
            self.config = self._merge_configs(self.default_config, loaded_config)
            
        except Exception as e:
            raise ValueError(f"加载配置文件失败: {str(e)}")
    
    def save_config(self, config_path: str, format_type: str = 'json') -> None:
        """
        保存配置到文件
        
        参数:
            config_path (str): 保存路径
            format_type (str): 文件格式 ('json' 或 'yaml')
            
        异常:
            ValueError: 文件格式不支持
            IOError: 文件写入失败
        """
        try:
            os.makedirs(os.path.dirname(config_path), exist_ok=True)
            
            if format_type.lower() == 'json':
                with open(config_path, 'w', encoding='utf-8') as f:
                    json.dump(self.config, f, indent=2, ensure_ascii=False)
            elif format_type.lower() in ['yaml', 'yml']:
                with open(config_path, 'w', encoding='utf-8') as f:
                    yaml.dump(self.config, f, indent=2, allow_unicode=True)
            else:
                raise ValueError(f"不支持的文件格式: {format_type}")
                
        except Exception as e:
            raise IOError(f"保存配置文件失败: {str(e)}")
    
    def get(self, key: str, default: Any = None) -> Any:
        """
        获取配置值
        
        参数:
            key (str): 配置键，支持点表示法 (例如: "drug.dose")
            default: 默认值
            
        返回:
            配置值
        """
        keys = key.split('.')
        value = self.config
        
        for k in keys:
            if isinstance(value, dict) and k in value:
                value = value[k]
            else:
                return default
        
        return value
    
    def set(self, key: str, value: Any) -> None:
        """
        设置配置值
        
        参数:
            key (str): 配置键，支持点表示法
            value: 要设置的值
        """
        keys = key.split('.')
        config = self.config
        
        for k in keys[:-1]:
            if k not in config:
                config[k] = {}
            config = config[k]
        
        config[keys[-1]] = value
    
    def update(self, updates: Dict[str, Any]) -> None:
        """
        批量更新配置
        
        参数:
            updates (dict): 更新字典
        """
        def _update_nested(config: Dict[str, Any], updates: Dict[str, Any]) -> None:
            for key, value in updates.items():
                if isinstance(value, dict) and key in config and isinstance(config[key], dict):
                    _update_nested(config[key], value)
                else:
                    config[key] = value
        
        _update_nested(self.config, updates)
    
    def validate(self) -> bool:
        """
        验证配置的有效性
        
        返回:
            bool: 配置是否有效
            
        异常:
            ValueError: 配置无效
        """
        errors = []
        
        # 验证药物参数
        drug_config = self.get('drug', {})
        required_drug_params = ['dose', 'fu', 'logP', 'pKa']
        for param in required_drug_params:
            if param not in drug_config:
                errors.append(f"缺少药物参数: {param}")
            elif drug_config[param] <= 0:
                errors.append(f"药物参数必须大于0: {param}")
        
        # 验证生理参数
        population_config = self.get('population', {})
        age = population_config.get('age', 0)
        weight = population_config.get('weight', 0)
        
        if age <= 0 or age > 120:
            errors.append("年龄必须在0-120之间")
        
        if weight <= 0 or weight > 300:
            errors.append("体重必须在0-300kg之间")
        
        # 验证模拟参数
        simulation_config = self.get('simulation', {})
        time_span = simulation_config.get('time_span', 0)
        time_step = simulation_config.get('time_step', 0)
        
        if time_span <= 0:
            errors.append("模拟时长必须大于0")
        
        if time_step <= 0 or time_step > time_span:
            errors.append("时间步长必须大于0且小于模拟时长")
        
        if errors:
            raise ValueError("配置验证失败:\n" + "\n".join(errors))
        
        return True
    
    def get_drug_config(self) -> Dict[str, Any]:
        """获取药物配置"""
        return self.get('drug', {})
    
    def get_absorption_config(self) -> Dict[str, Any]:
        """获取吸收配置"""
        return self.get('absorption', {})
    
    def get_distribution_config(self) -> Dict[str, Any]:
        """获取分布配置"""
        return self.get('distribution', {})
    
    def get_elimination_config(self) -> Dict[str, Any]:
        """获取消除配置"""
        return self.get('elimination', {})
    
    def get_population_config(self) -> Dict[str, Any]:
        """获取人群配置"""
        return self.get('population', {})
    
    def get_simulation_config(self) -> Dict[str, Any]:
        """获取模拟配置"""
        return self.get('simulation', {})
    
    def get_output_config(self) -> Dict[str, Any]:
        """获取输出配置"""
        return self.get('output', {})
    
    def _merge_configs(self, default: Dict[str, Any], override: Dict[str, Any]) -> Dict[str, Any]:
        """
        合并配置字典
        
        参数:
            default: 默认配置
            override: 覆盖配置
            
        返回:
            合并后的配置
        """
        merged = default.copy()
        
        for key, value in override.items():
            if key in merged and isinstance(merged[key], dict) and isinstance(value, dict):
                merged[key] = self._merge_configs(merged[key], value)
            else:
                merged[key] = value
        
        return merged
    
    def reset_to_default(self) -> None:
        """重置为默认配置"""
        self.config = self.default_config.copy()
    
    def get_model_params(self) -> Dict[str, Any]:
        """
        获取合并后的模型参数
        
        返回:
            Dict[str, Any]: 包含所有模型参数的字典
        """
        model_params = {}
        
        # 合并所有配置到一个参数字典中
        drug_config = self.get_drug_config()
        absorption_config = self.get_absorption_config()
        distribution_config = self.get_distribution_config()
        elimination_config = self.get_elimination_config()
        population_config = self.get_population_config()
        simulation_config = self.get_simulation_config()
        
        model_params.update(drug_config)
        model_params.update(absorption_config)
        model_params.update(distribution_config)
        model_params.update(elimination_config)
        model_params.update(population_config)
        model_params.update(simulation_config)
        
        return model_params
    
    def get_individual_params(self, individual_data: Dict[str, Any]) -> Dict[str, Any]:
        """
        获取个体特定参数
        
        参数:
            individual_data (Dict[str, Any]): 个体数据
            
        返回:
            Dict[str, Any]: 个体特定参数
        """
        base_params = self.get_model_params()
        individual_params = base_params.copy()
        
        # 更新个体特定参数
        individual_params.update(individual_data)
        
        return individual_params


class ConfigValidator:
    """
    配置验证器类
    
    提供各种配置参数的验证方法
    """
    
    @staticmethod
    def validate_positive(value: float, name: str) -> None:
        """验证正值"""
        if value <= 0:
            raise ValueError(f"{name}必须大于0")
    
    @staticmethod
    def validate_range(value: float, min_val: float, max_val: float, name: str) -> None:
        """验证范围"""
        if value < min_val or value > max_val:
            raise ValueError(f"{name}必须在{min_val}-{max_val}之间")
    
    @staticmethod
    def validate_choice(value: str, choices: list, name: str) -> None:
        """验证选择项"""
        if value not in choices:
            raise ValueError(f"{name}必须是以下之一: {', '.join(choices)}")
    
    @staticmethod
    def validate_file_path(path: str, name: str) -> None:
        """验证文件路径"""
        if path and not os.path.exists(path):
            raise ValueError(f"{name}文件不存在: {path}")


# 创建全局配置管理器实例
config_manager = ConfigManager()